﻿using Microsoft.CSharp.RuntimeBinder;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using Binder = Microsoft.CSharp.RuntimeBinder.Binder;

namespace CallSiteAndExpressionTrees
{
    public class Member
    {
        internal virtual object GetValue(object obj)
        {
            return null;
        }
        internal virtual void SetValue(object obj, object v)
        {
        }

        internal virtual void SetAction(object getter, object setter)
        {
        }
    }

    public class Member<T, T1> : Member
    {
        public Func<T, T1> getter;
        public Action<T, T1> setter;

        internal override object GetValue(object obj)
        {
            return getter((T)obj);
        }
        internal override void SetValue(object obj, object v)
        {
            setter((T)obj, (T1)v);
        }
        internal override void SetAction(object getter, object setter)
        {
            this.getter = (Func<T, T1>)getter;
            this.setter = (Action<T, T1>)setter;
        }
    }

    public class Member<T> : Member
    {
        internal CallSite<Func<CallSite, object, object>> getValueCall;
        internal CallSite<Func<CallSite, object, T, object>> setValueCall;
        internal override object GetValue(object obj)
        {
            return getValueCall.Target(getValueCall, obj);
        }
        internal override void SetValue( object obj, object v)
        {
            setValueCall.Target(setValueCall, obj, (T)v);
        }
        internal override void SetAction(object getter, object setter)
        {
            getValueCall = getter as CallSite<Func<CallSite, object, object>>;
            setValueCall = setter as CallSite<Func<CallSite, object, T, object>>;
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            Member member1 = (Member)Activator.CreateInstance(typeof(Member<>).MakeGenericType(typeof(int)));

            var getValueCall = CallSite<Func<CallSite, object, object>>.Create(Binder.GetMember(0, "f1", typeof(Test), new[]
            {
                CSharpArgumentInfo.Create(0, null)
            }));
            var csType = typeof(Func<,,,>).MakeGenericType(typeof(CallSite), typeof(object), typeof(int), typeof(object));
            var setValueCall = (CallSite<Func<CallSite, object, int, object>>)CallSite.Create(csType, Binder.SetMember(0, "f1", typeof(Test), new[]
            {
                CSharpArgumentInfo.Create(0, null),
                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null)
            }));

            member1.SetAction(getValueCall, setValueCall);

            Member member = (Member)Activator.CreateInstance(typeof(Member<,>).MakeGenericType(typeof(Test), typeof(int)));

            var param = Expression.Parameter(typeof(Test), "obj");
            var field = Expression.Field(param, typeof(Test).GetField("f1", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
            var getter = Expression.Lambda<Func<Test, int>>(field, param).Compile();

            var paramObj = Expression.Parameter(typeof(Test), "obj");
            var paramValue = Expression.Parameter(typeof(int), "value");
            field = Expression.Field(paramObj, typeof(Test).GetField("f1", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
            var assign = Expression.Assign(field, paramValue);
            var setter = Expression.Lambda<Action<Test, int>>(assign, paramObj, paramValue).Compile();

            member.SetAction(getter, setter);

            Task.Run(() =>
            {
                while (true)
                {
                    object test = new Test();
                    var stopwatch = Stopwatch.StartNew();
                    for (int i = 0; i < 100000000; i++)
                    {
                        member.SetValue(test, 123);
                        var v = member.GetValue(test);
                    }
                    stopwatch.Stop();
                    Console.WriteLine("Expression 1000万次:" + stopwatch.Elapsed);
                }
            });

            Task.Run(() =>
            {
                while (true)
                {
                    object test = new Test();
                    var stopwatch = Stopwatch.StartNew();
                    for (int i = 0; i < 100000000; i++)
                    {
                        member1.SetValue(test, 123);
                        var v = member1.GetValue(test);
                    }
                    stopwatch.Stop();
                    Console.WriteLine("CallSite 1000万次:" + stopwatch.Elapsed);
                }
            });

            Console.ReadLine();
        }
    }

    public class Test
    {
        public int f1;
    }
}
